package com.youlv.yl.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.youlv.yl.bo.UserNameLoginBo;
import com.youlv.yl.bo.UserPhoneLoginBo;
import com.youlv.yl.config.RedisKeyConfig;
import com.youlv.yl.config.SystemConfig;
import com.youlv.yl.config.UserScoreConfig;
import com.youlv.yl.dao.*;
import com.youlv.yl.dto.UserAddDto;
import com.youlv.yl.entity.user.User;
import com.youlv.yl.entity.user.UserLevel;
import com.youlv.yl.entity.user.UserScoreLog;
import com.youlv.yl.entity.user.Userlog;
import com.youlv.yl.enumerate.UserOpType;
import com.youlv.yl.service.intf.UserLevelService;
import com.youlv.yl.service.intf.UserService;
import com.youlv.yl.util.*;
import com.youlv.yl.vo.R;
import org.apache.catalina.startup.UserConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author fenghe
 * @since 2022-01-02
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao dao;
    @Autowired
    private UserlogDao logDao;
    @Autowired
    private UserScoreLogDao scoreLogDao;
    @Autowired
    private UserLevelDao levelDao;
    @Autowired
    private UserLevelService levelService;

    @Override
    @Transactional
    public R login(UserNameLoginBo bo, String ip) {
        // 1.入参校验
        if(bo != null && StringUtils.isNotEmpty(bo.getUsername(), bo.getPassword())) {
            // 2.判断用户名是否存在
            User user = checkName(bo.getUsername());
            if(user == null) {
                // 用户名不存在，
                return R.fail("用户名不存在");
            }
            if(user.getFlag().equals( SystemConfig.USER_FLAG_FREEZE)) {
                return R.fail("亲，您的账号已被冻结，请联系客服！！！");
            }
            // 3.密码转为密文
            bo.setPassword(EncryptUtil.aesenc(SystemConfig.PASS_KEY, bo.getPassword()));
            // 4.校验密码
            if(user.getPassword().equals(bo.getPassword())) {
                // 5.密码正确，key生成令牌
                String token = TokenUtils.createToken(user.getPhone(), user.getId() + "");
                // 判断当前在线数量
                long loginCount = JedisUtils.getListSize("USER_LOGIN_COUNT");
//                // 若在线人数两人及以上，拒绝登陆请求
//                if(loginCount >= 2) {
//                    return R.fail(100, "亲，当前账号在线人数过多，请稍后重试哦！！！");
//                } else {
//                    // 在线人数少于两人，将状态码存入Redis
//                    JedisUtils.addListStart("USER_LOGIN_COUNT", token);
//                    // 设置集合有效期
//                    JedisUtils.expire("USER_LOGIN_COUNT", RedisKeyConfig.USER_TOKEN_TIME);
//                }
                // 记录登陆日志
                logDao.insert(new Userlog(user.getId(), ip, "实现了账号的登陆", UserOpType.登陆.getValue()));
                // 增加登陆积分
                addLoginScore(user.getPhone(), user.getId(), token);
                return R.ok(token);
            } else {
                // 密码错误，将账号存入Redis，若五分钟内输错三次密码，冻结账号，一天内输错5次冻结账号
                // 判断当天密码错误是否五次
                // 获取集合长度
                Long listSize = JedisUtils.getListSize(RedisKeyConfig.PASSWORD_ERROR + user.getUsername());
                if(listSize < RedisKeyConfig.PASSWORD_ERROR_COUNT) {
                    // 存入密码错误记录
                    JedisUtils.addListEnd(RedisKeyConfig.PASSWORD_ERROR + user.getUsername(), DateUtils.CurrentTime());
                    // 设置集合有效期为一天
                    JedisUtils.expire(RedisKeyConfig.PASSWORD_ERROR + user.getUsername(), RedisKeyConfig.PASSWORD_ERROR_TIME);
                    // 密码错误超过两次，判断五分钟内是否错误三次
                    if(listSize > 1) {
                        // 获取上两次密码错误时间
                        String startTime = JedisUtils.getListParamByIndex(RedisKeyConfig.PASSWORD_ERROR + user.getUsername(),
                                listSize - 2);
                        // 获取本次密码错误时间
                        String endTime = JedisUtils.getListParamByIndex(RedisKeyConfig.PASSWORD_ERROR + user.getUsername(),
                                listSize);
                        // 比较三次输错密码时间差
                        if(DateUtils.getTimeDiff(endTime, startTime) < 5) {
                            // 五分钟内输错密码超过3次,冻结账号
                            user.setFlag(SystemConfig.USER_FLAG_FREEZE);
                            UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
                            updateWrapper.eq("username", user.getUsername()).set("flag", user.getFlag());
                            dao.update(null, updateWrapper);
                            return R.fail("亲，您密码错误次数过多，账号已被冻结，请联系客服！！！");
                        }
                    }
                    return R.fail("密码错误");
                } else {
                    // 一天内输错密码超过五次,冻结账号
                    user.setFlag(SystemConfig.USER_FLAG_FREEZE);
                    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
                    updateWrapper.eq("username", user.getUsername()).set("flag", user.getFlag());
                    dao.update(null, updateWrapper);
                    return R.fail("亲，您密码错误次数过多，账号已被冻结，请联系客服！！！");
                }
            }
        }
        return R.fail("非法请求");
    }

    /**开启事务*/
    @Transactional
    @Override
    public R login(UserPhoneLoginBo bo, String ip) {
        // 1.入参验证
        if(bo != null && StringUtils.isNotEmpty(bo.getPhone()) && bo.getPhone().matches(SystemConfig.REG_PHONE)) {
            // 2.判断手机号在数据库是否存在
            User user = this.checkPhone(bo.getPhone());
            if(user == null) {
                // 3.手机号不存在，直接注册 将手机号存入数据库
                user = new User();
                user.setPhone(bo.getPhone());
                dao.insert(user);
                // 4.记录注册日志
                logDao.insert(new Userlog(user.getId(), ip, "实现了账号的注册并登录", UserOpType.注册.getValue()));
                // 增加注册积分
                levelDao.insert(new UserLevel(user.getId(), UserScoreConfig.SIGN_SCORE_F, UserScoreConfig.SIGN_SCORE_F, UserScoreConfig.USER_SCORE_LEVEL_1));
                // 增加积分日志
                scoreLogDao.insert(new UserScoreLog(user.getId(), UserScoreConfig.SIGN_SCORE_F, "完成注册，奖励20积分"));
            } else {
                // 5.手机号存在，直接登录
                // 记录登陆日志
                logDao.insert(new Userlog(user.getId(), ip, "实现了手机号验证码登陆", UserOpType.登陆.getValue()));
            }
            // 6.生成双令牌，并存入Redis
            String token = TokenUtils.createToken(bo.getPhone(), user.getId() + "");
            // 增加登陆积分
            addLoginScore(user.getPhone(), user.getId(), token);
            // 7.返回令牌
            return R.ok(token);
        }
        return R.fail("非法请求");
    }

    /**开启事务*/
    @Transactional
    @Override
    public R register(UserAddDto dto, String ip) {
        // 入参校验
        if(dto != null && StringUtils.isNotEmpty(dto.getPhone(), dto.getPassword()) && dto.getPhone().matches(SystemConfig.REG_PHONE)) {
            // 查询手机号是否存在
            User user = checkPhone(dto.getPhone());
            if(user != null) {
                // 手机号已存在，停止注册，返回状态码，询问用户是否直接登录
                return R.ok(SystemConfig.R_NUM_ONE);
            } else {
                //基于反射 创建类对象
                user = BeanUtils.copyBean(User.class, dto, dto.getClass().getDeclaredFields());
                // 密码转为密文
                user.setPassword(EncryptUtil.aesenc(SystemConfig.PASS_KEY, user.getPassword()));
                // 将用户添加到数据库
                dao.insert(user);
                // 记录日志
                logDao.insert(new Userlog(user.getId(), ip, "实现了账号的注册", UserOpType.注册.getValue()));
                // 增加注册积分
                levelDao.insert(new UserLevel(user.getId(), UserScoreConfig.SIGN_SCORE_F, UserScoreConfig.SIGN_SCORE_F, UserScoreConfig.USER_SCORE_LEVEL_1));
                // 增加积分日志
                scoreLogDao.insert(new UserScoreLog(user.getId(), UserScoreConfig.SIGN_SCORE_F, "完成注册，奖励20积分"));
                // 生成双令牌，并存入Redis
                String token = TokenUtils.createToken(dto.getPhone(), user.getId() + "");
                // 返回token
                return R.ok(token);
            }
        } else {
            return R.fail("网络繁忙，请稍后重试");
        }
    }

    /**开启事务*/
    @Transactional
    @Override
    public R findPassword(UserAddDto dto, String ip) {
        // 入参验证
        if(dto != null && StringUtils.isNotEmpty(dto.getPhone(), dto.getPassword()) && dto.getPhone().matches(SystemConfig.REG_PHONE)) {
            // 判断手机号是否存在
            User user = this.checkPhone(dto.getPhone());
            if(user != null) {
                // 手机号存在，通过手机号修改密码
                // 基于反射，将手机号和修改后的密码赋值user对象
                user = BeanUtils.copyBean(User.class, dto, dto.getClass().getDeclaredFields());
                // 将密码设为密文
                user.setPassword(EncryptUtil.aesenc(SystemConfig.PASS_KEY, user.getPassword()));
                // 创建构造器
                UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("phone", user.getPhone()).set("password", user.getPassword());
                // 修改数据库密码
                if(dao.update(null, updateWrapper) > 0) {
                    // 修改成功，记录日志
                    logDao.insert(new Userlog(user.getId(), ip, "实现了账号的密码找回", UserOpType.找回密码.getValue()));
                    // 校验账号是否在线，如果在线清空令牌信息
                    if(JedisUtils.exists(RedisKeyConfig.USER_PHONE + dto.getPhone())) {
                        // 令牌存在，在线，需要退出
                        String tk = JedisUtils.getString(RedisKeyConfig.USER_PHONE + dto.getPhone());
                        JedisUtils.delKey(RedisKeyConfig.USER_TOKEN + tk);
                        JedisUtils.delKey(RedisKeyConfig.USER_PHONE + dto.getPhone());
                    }
                    return R.ok();
                } else {
                    // 重置密码失败
                    return R.fail("网络繁忙，请稍后重试");
                }
            } else {
                // 手机号不存在，
                return R.ok(SystemConfig.R_NUM_ONE);
            }
        } else {
            return R.fail("非法请求");
        }
    }

    @Override
    public User checkPhone(String phone) {
        // 入参校验
        if(StringUtils.isNotEmpty(phone) && phone.matches(SystemConfig.REG_PHONE)) {
            // 构建查询条件
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("phone", phone);
            // 执行查询结果
            return dao.selectOne(queryWrapper);
        } else {
            return null;
        }
    }

    @Override
    public User checkName(String name) {
        // 入参校验
        if(StringUtils.isNotEmpty(name)) {
            // 构建查询条件
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("username", name);
            // 执行查询结果
            return dao.selectOne(queryWrapper);
        }
        return null;
    }

    @Override
    public R loginOut(String token) {
        // 入参校验
        if(StringUtils.isNotEmpty(token)) {
            // 查询令牌是否存在
            String key = RedisKeyConfig.USER_TOKEN + token;
            if(JedisUtils.exists(key)) {
                // 删除令牌，获取用户id
                Long id = TokenUtils.getUidByToken(token);
                // 删除用户手机号令牌
                JedisUtils.delKey(RedisKeyConfig.USER_PHONE +
                        JedisUtils.getHash(RedisKeyConfig.USER_UID_PHONE, id + ""));
                // 删除用户id+手机号hash令牌
                JedisUtils.delHash(RedisKeyConfig.USER_UID_PHONE, id + "");
                // 删除token令牌
                JedisUtils.delKey(key);
                return R.ok();
            }
        }
       return R.fail("请检查用户信息");
    }

    @Override
    public R checkToken(String token) {
        return null;
    }

    /** 检验今天是否是第一次登陆*/
    public boolean isLoginFirst(String phone) {
        // 返回true为第一次登陆
        return !DateUtils.TodayDate().equals(JedisUtils.getString(RedisKeyConfig.IS_FIRST_LOGIN + phone));
    }

    /** 增加第一次登陆积分*/
    public void addLoginScore(String phone, Long id, String token) {
        // 不相同。说明，今日第一次登陆，增加登陆积分，并将今日登陆状态存入Redis
        if(isLoginFirst(phone)) {
            // 添加今日时间为状态存入Redis，用来判断今日是否是第一次登陆
            JedisUtils.addString(RedisKeyConfig.IS_FIRST_LOGIN + phone, DateUtils.TodayDate(), RedisKeyConfig.IS_FIRST_LOGIN_TIME);
            // 查询当前用户积分
            UserLevel userLevel = (UserLevel)levelService.queryScore(token).getData();
            // 增加登陆积分
            userLevel.setScore(userLevel.getScore() + UserScoreConfig.LOGIN_SCORE);
            levelService.save(id, UserScoreConfig.LOGIN_SCORE, "今日登陆，积分+1");
        }
    }
}
